#include <iostream>
#include <time.h>
#include <mpi.h>

using namespace std;

#define ROWS		8
#define COLUMNS		6


unsigned char **currentUniverse;
unsigned char **nextUniverse;
unsigned char **globalUniverse;

int processes; //number of processes
int myrank; //process rank
int *elements; //vector containg the number of rows received from the master by the i-th process
int maxElem; //max elements given to each process

enum spawnConfig { DEBUG, RANDOM, FROM_FILE }; //options for the initial seed


//fill the start matrix according to the specified option
void spawnSeed(unsigned char** universe, spawnConfig config)
{

	switch(config)
	{
		case RANDOM:
		{
			srand (time(0));
			for (int i = 0; i < ROWS + 2; i++)
				for (int j = 0; j < COLUMNS + 2; j++)
				{
					/*create a border of fake dead cells around the universe of dead cells. This is used to simulate 
					the behaviour of cells at the border.
					*/
					if (i == 0 || j == 0 || i == ROWS + 1 || j == COLUMNS + 1)
						universe[i][j] = 0;
					else
					{
						int choice = rand() % 100;
						if (choice > 15)
							universe[i][j] = 1;
						else
							universe[i][j] = 0;
					}
				}

			break;
		}

		default:
		{
			cout << "ERROR: Invalid config option in spawnSeed" << endl;
		}
	}
}

//this function takes a 2D-vector and returns the equivalent 1D-vector representing the matrix
unsigned char *buildLinearMatrix(unsigned char **matrix, int rows, int columns)
{
	unsigned char *matrixVector = new unsigned char[rows * columns];
	
	int k = 0;
	for (int i = 0; i < rows; i++)
		for (int j = 0; j < columns; j++)
			matrixVector[k++] = matrix[i][j];

	return matrixVector;
}

//this function takes a 1D-vector and returns the equivalent 2D matrix
unsigned char **buildMatrixFromVector(unsigned char *vector, int rows, int columns)
{
	unsigned char **matrix = new unsigned char*[rows];
	if (matrix == NULL)
		cerr << "Can't allocate memory in buildMatrixFromVector \n";
	for (int i = 0; i < columns; i++)
	{
		matrix[i] = new unsigned char[columns];
		if (matrix[i] == NULL)
			cerr << "Can't allocate memory in buildMatrixFromVector for element i \n";
	}
	
	int k = 0;
	for (int i = 0; i < rows; i++)
		for (int j = 0; j < columns; j++)
			matrix[i][j] = vector[k++];

	return matrix;

}

void drawUniverse(unsigned char** universe)
{
	for (int i = 1; i < ROWS + 1; i++)
		for (int j = 1; j < COLUMNS + 1; j++)
		{
			cout << (universe[i][j] == 1 ? "O" : " ");
			if (j == COLUMNS) cout << "\n";
		}
	cout << "\n\n";
}

void drawMatrix(unsigned char** matrix, int rows, int columns)
{
	for (int i = 0; i < columns; i++)
		for (int j = 0; j < rows; j++)
		{
			cout << (matrix[i][j] == 1 ? "0" : "X");
			if (j == columns - 1) cout << "\n";
		}
	cout << "\n\n";
}

void distributePartialMatrices()
{
	unsigned char *universeVector;
	int *sendCounts = new int[processes];
	int *offset = new int[processes];
	elements = new int[processes];

	for (int i = 0; i < processes; i++)
	{
		sendCounts[i] = 0;
		offset[i] = 0;
		elements[i] = 0;
	}
	
	/*elements = (int)(ceil((double)(ROWS + 2) / (double)processes));
	maxElem = elements;
	
	if (myrank == processes - 1)
		elements = (ROWS + 2) - ((processes - 1) * maxElem);

	for (int i = 0; i < processes; i++)
	{
		if (i == processes - 1)
			sendCounts[i] = ((ROWS + 2) - ((processes - 1) * maxElem)) * (COLUMNS + 2);
		else
			sendCounts[i] = elements * (COLUMNS + 2);
		offset[i] = maxElem * i * (COLUMNS + 2);
	}*/

	int pcount = 0;
	int elementsLeft = ROWS + 2;

	while (elementsLeft > 0)
	{
		sendCounts[pcount]++;
		elements[pcount]++;
		elementsLeft--;
		pcount = (pcount + 1) % processes;
	}

	for (int i = 0; i < processes; i++)
		sendCounts[i] *= COLUMNS + 2;

	for (int i = 0; i < processes; i++)
	{
		if (i == 0)
			offset[i] = 0;
		else
			offset[i] = offset[i - 1] + sendCounts [i - 1];
	}

	if (myrank == 0)
	{
		universeVector = buildLinearMatrix(globalUniverse, ROWS + 2, COLUMNS + 2);
		//DEBUG
		cout << "Universe Vector:\n\n";
		for (int i = 0; i < (ROWS + 2) * (COLUMNS + 2); i++)
			cout << (universeVector[i] == 1 ? "0" : "X");
		cout << "\n\n";

		cout << "Send Vector:\n\n";
		for (int i = 0; i < processes; i++)
			cout << sendCounts[i] << " ";
		cout << "\n\n";

		cout << "Offset Vector:\n\n";
		for (int i = 0; i < processes; i++)
			cout << offset[i] << " ";
		cout << "\n\n";
	}

	unsigned char *recBuffer = new unsigned char[sendCounts[myrank]];

	//DEBUG
	/*for (int i = 0; i < sendCounts[myrank]; i++)
	{
		recBuffer[i] = 0;
		cout << "Elements of buffer " << myrank << " are " << i + 1;
	}*/
	
	MPI_Scatterv(universeVector, sendCounts, offset, MPI_UNSIGNED_CHAR, recBuffer, sendCounts[myrank], MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD);

	//DEBUG
	cout << "Rec Buffer of process " << myrank << ": \n\n";
	for (int i = 0; i < sendCounts[myrank]; i++)
		cout << (recBuffer[i] == 1 ? "0" : "X");
	cout << "\n\n";
	
	//currentUniverse = buildMatrixFromVector(recBuffer, elements[myrank], COLUMNS + 2);

	//DEBUG
	/*cout << "Partial matrix of process " << myrank << " is \n";
	drawMatrix(currentUniverse, elements[myrank], COLUMNS + 2);*/
}

int main(int argc, char *argv[])
{
	MPI_Init(&argc, &argv);
	MPI_Comm_size(MPI_COMM_WORLD, &processes);
	MPI_Comm_rank(MPI_COMM_WORLD, &myrank);

	/*The master must fill the starting universe matrix either with random cells or from a given configuration file
	  then it will distribute the start matrix rows among the various processes.
	*/
	if (myrank == 0)
	{
		//initialize the matrix
		globalUniverse = new unsigned char*[ROWS + 2];
		for (int i = 0; i < ROWS + 2; i++)
			globalUniverse[i] = new unsigned char[COLUMNS + 2];
		
		spawnSeed(globalUniverse, RANDOM);
		
		//print start matrix
		drawUniverse(globalUniverse);
	}

	distributePartialMatrices();

	MPI_Barrier(MPI_COMM_WORLD);

	MPI_Finalize();
	
	return 0;
}